# -*- coding: utf-8 -*-
# @Time     : 2020/8/22 10:48 上午
# @Author   : huangxiong
# @FileName : brinson_attribution.py
# @Comment  : brinson权益归因
# @Software : PyCharm
import pandas as pd

from quant_researcher.quant.performance_attribution.core_functions.holding_analysis.holding_analysis import \
    get_latest_industry_allocation
from quant_researcher.quant.datasource_fetch.portfolio_api import portfolio_tool
from quant_researcher.quant.datasource_fetch.index_api.index_info import get_sw_industry_name
from quant_researcher.quant.datasource_fetch.stock_api import stock_price_related
from quant_researcher.quant.project_tool.exception import FOFServerError, ERR_S_2, FOFUserError, ERR_U_1, ERR_U_2


def portfolio_brinson_attribution(portfolio_id, start_date, end_date, customer_create, index_code, customer_index):
    """
    得到一段时间组合的brinson权益归因结果

    :param portfolio_id: 组合ID
    :param start_date: 开始时间，格式"20190601"
    :param end_date: 结束时间，格式"20200810"
    :param customer_create: 是否是用户自建指数，1-是，0-否，默认0
    :param index_code: 业绩基准代码
    :param customer_index: 用户自建指数，customer_create=1时必须传入该参数。
                           需要传入构建自建指数所需要的指数代码及权重，格式{"000001": 0.5, "000300": 0.5}
    :return:
    """
    # 组合各基金每天的权重和收益率
    f_weight = portfolio_tool.get_portfolio_fund_weight(portfolio_id, start_date, end_date)
    if f_weight is None:
        raise FOFServerError(ERR_S_2, f"未找到该组合 {portfolio_id} 的信息")
    fund_list = list(f_weight.columns)

    # 组合的申万行业配置权重
    p_sw_df = get_latest_industry_allocation(fund_list, end_date)
    if p_sw_df is None:
        raise FOFServerError(ERR_S_2, f"未找到该组合中基金{fund_list}的持仓信息")
    p_sw_df = p_sw_df.reindex(f_weight.columns)

    # 组合在时间区间内的平均申万行业配置权重
    p_sw_w_df = p_sw_df.mul(f_weight.mean(), axis=0).sum().reset_index()
    p_sw_w_df = p_sw_w_df.rename(columns={0: 'p_industry_weight'})

    # 基准的申万行业配置权重
    if (customer_create == 1 and customer_index is None) or \
            (customer_create == 0 and index_code is None):
        raise FOFUserError(ERR_U_1, "如果customer_create为1，需要输入customer_index，"
                                    "如果customer_create为0，需要输入index_code，"
                                    "请按要求重新输入")
    if customer_create == 0:
        b_sw_w_df = get_latest_industry_allocation([index_code], end_date, asset_type='index')
        if b_sw_w_df is None:
            raise FOFServerError(ERR_S_2, f"未找到基准{index_code}的成分股信息")
        b_sw_w_df = b_sw_w_df.T.reset_index()
        b_sw_w_df = b_sw_w_df.rename(columns={index_code: 'b_industry_weight'})
    else:
        tmp_list = []
        for i in customer_index.keys():
            tmp_df = get_latest_industry_allocation([i], end_date, asset_type='index')
            if tmp_df is not None:
                tmp_df = tmp_df.mul(customer_index[i])
                tmp_list.append(tmp_df)
        if len(tmp_list) == 0:
            raise FOFServerError(ERR_S_2, f"未找到基准{customer_index}的成分股信息")
        b_sw_w_df = pd.concat(tmp_list)
        b_sw_w_df = b_sw_w_df.sum().reset_index()
        b_sw_w_df = b_sw_w_df.rename(columns={0: 'b_industry_weight'})

    sw_w_df = p_sw_w_df.merge(b_sw_w_df, how='outer', on='industry_code')
    sw_w_df['industry_weight_diff'] = sw_w_df['p_industry_weight'] - sw_w_df['b_industry_weight']
    sw_w_df = sw_w_df.set_index('industry_code')

    # 基准成份股的申万行业分类
    if customer_create == 0:
        b_sw_stock_df = get_latest_industry_allocation([index_code], end_date, asset_type='index', detail_stock=True)
    else:
        tmp_list = []
        for i in customer_index.keys():
            tmp_df = get_latest_industry_allocation([i], end_date, asset_type='index', detail_stock=True)
            if tmp_df is not None:
                tmp_df['stock_weight'] = tmp_df['stock_weight'] * customer_index[i]
                tmp_list.append(tmp_df)
        if len(tmp_list) == 0:
            raise FOFServerError(ERR_S_2, f"未找到基准{customer_index}的成分股信息")
        b_sw_stock_df = pd.concat(tmp_list)
        b_sw_stock_df = b_sw_stock_df.dropna(subset=['industry_code'])
        b_sw_stock_df = b_sw_stock_df.groupby(['stock_code', 'industry_code'])['stock_weight'].sum().reset_index()
    b_sw_stock_df = b_sw_stock_df.set_index(['industry_code', 'stock_code'])['stock_weight'].unstack()
    b_sw_stock_df = b_sw_stock_df.fillna(0)
    b_sw_stock_df = b_sw_stock_df.div(b_sw_stock_df.sum(axis=1), axis=0)

    # 股票每天的收益率
    # stock_ret = common_functions.get_stock_daily_ret_between_days(start_date, end_date)
    stock_ret = stock_price_related.get_stock_quote(start_date=start_date, end_date=end_date,
                                                    adj_type='b', market_type='both',
                                                    select=['stock_code', 'end_date', 'updown_percent as ret'])
    stock_ret['end_date'] = stock_ret['end_date'].apply(lambda x: x.replace('-', ''))
    stock_ret = stock_ret.set_index(['end_date', 'stock_code'])['ret'].unstack()
    if stock_ret.shape[0] <= 1:
        raise FOFUserError(ERR_U_2, f"{start_date}到{end_date}的股票行情数据不足两天，无法计算收益率的标准差")
    stock_ret = stock_ret.reindex(columns=b_sw_stock_df.columns)
    stock_ret = stock_ret.fillna(0)
    # 基准在每个行业每天的收益率
    b_ret = stock_ret.groupby(stock_ret.index).apply(lambda x: (b_sw_stock_df.mul(x.iloc[0, :])).sum(axis=1))
    b_ret_std = b_ret.std()
    # 基准在每个行业的累计收益率
    b_ret_cum = (b_ret + 1).product() - 1

    # 计算主动配置收益和主动配置风险
    sw_w_df['active_ret'] = sw_w_df['industry_weight_diff'] * b_ret_cum
    sw_w_df['active_std'] = sw_w_df['industry_weight_diff'] * b_ret_std
    sw_w_df = sw_w_df.fillna(0)
    sw_w_df = sw_w_df.round(4)
    sw_w_df = sw_w_df.reset_index()

    industry_name = get_sw_industry_name()
    sw_w_df = sw_w_df.merge(industry_name, how='left', on='industry_code')
    res_list = sw_w_df.to_dict(orient='records')
    return res_list


if __name__ == '__main__':
    # test = portfolio_brinson_attribution(portfolio_id=1076, start_date='2019-12-01', end_date='2020-09-01',
    #                                      customer_create=0, index_code='000300', customer_index=None)
    aaa = portfolio_brinson_attribution(portfolio_id=1352, start_date='2021-07-01', end_date='2021-08-01',
                                        customer_create=1, index_code=None, customer_index={"000001": 0.5, "000300": 0.5})